package agent.strategy;

/**
 * This strategy goes to try to explore the closest terminal that is not known
 * the returned move has a certain probability of success of getting to a tile where it can explore
 * any one of the terminals within commitTime and where there is not another terminal
 * within softCommitTime with probability at least probCutOff of reaching
 * @author mdailey
 *
 */
public class ExploreNearestTerminal extends Strategy{

	public ExploreNearestTerminal(BoardMemory myBoard) {
		super(myBoard);
	}

	/**
	 * Go to the place where we will explore from
	 * we return a move that has a probability of success
	 * -- commitTime should be one less than the time needed to get there and explore
	 * @param commitTime This is how far away we will have to move towards checking terminal
	 * @param softCommitTime (commitTime) Do not value exploring at a tile if we can get to a terminal within this many moves
	 * @param probCutOff (.6)The min prob for which we have softCommitTime to get to a terminal
	 */
	public nextDirObject nextMove(double commitTime, double softCommitTime, double probCutOff){
		/* we need to give values to squares where we can view if there exists a terminal
		 * and there is not a terminal within reach of us
		 * */
		boolean excludeSquare[][] = new boolean[this.maxRow][this.maxCol];
		boolean modTerminals[][] = new boolean[this.maxRow][this.maxCol];	// where we can explore from
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				for(int x=0; x< this.maxRow; x++){
					for(int y=0; y<this.maxCol; y++){
						if(terminals[x][y]){
							double prob = this.probReachSquareFromTo(r, c, x, y, (int)softCommitTime).probSuccess;
							if(prob > probCutOff)	// we can reach this terminal with high probabilty
								excludeSquare[r][c] = true;
						}
						if(this.isReachableWithin(r, c, x, y, (int) commitTime) && this.pastTerminals[r][c] && !this.terminals[r][c])
							modTerminals[r][c] = true;
					}
				}
			}
		}
		/* now we know what squares have a terminal reachable, so weight all other squares */
		double values[][] = new double[this.maxRow][this.maxCol];
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				if(excludeSquare[r][c])
					values[r][c] = 0;
				for(int x=0; x< this.maxRow; x++){
					for(int y=0; y<this.maxCol; y++){
						if(pastTerminals[x][y] && this.isReachable(r, c, x, y)){
							values[r][c] = 1;
						}
					}
				}
			}
		}
		
		// modTerminals are true from where we will expose a past terminal, but we should
		// exclude places where we can already reach a terminal
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				modTerminals[r][c]= !excludeSquare[r][c] && modTerminals[r][c];
			}
		}
		
		/* now we maximize our utility */ // first arg might be values
		nextDirObject myMoves = this.getNextDirection(new double[maxRow][maxCol], modTerminals, walls, null, explored, this.currRow,
				this.currColumn, this.commitTime, 0, this.maxRow, this.maxCol, this.commitTime, 0);
		myMoves.probSuccess = myMoves.expectedUtility;
		myMoves.adviseMove = true;
		return myMoves;
	}

	
	/**
	 * This calculated the expected utility of trying to go from the current location
	 * To a tile that we can explore an old terminal from
	 * And then estimate the cost from that tile to the found terminal
	 * 
	 * Calculates the expected number of moves to reach a square where you can do it from
	 * 
	 * In other words we sum over the probabilities of getting to a tile in k moves*cost + Cost(n-k moves,tile,terminal)
	 * @return
	 */
	public nextDirObject expectedUtil(int commitTime){
		this.setUpFields();
		this.setUpBMBoards();
		/* 
		 * we are going to return Sum_k=1^commit_time Pr(reaching spot in no fewer than k)*cost_k_moves
 		 */
		nextDirObject best = nextMoveA(commitTime,commitTime,0);
		nextDirObject currStep = nextMove(commitTime,commitTime,0);
		double expUtility = 0;
		double totalProb = 0;	// prob of reaching such a state any of cTimeMoves
		double oldProb = 0;	
		double currProb = 0;	// used to iterate through prob
		double probDiff = 0;
		
		for(int cTime = 0; cTime<commitTime; cTime++){
			currProb = this.nextMoveA(cTime, 0, 1).probSuccess;
			probDiff = currProb - oldProb;
			expUtility+= probDiff*cTime;
			oldProb = currProb;
			totalProb+= probDiff;
		}
		System.out.println("Total prob of getting there " + totalProb + " with utility " + expUtility);
		best.expectedUtility = expUtility;
		best.probSuccess = totalProb;
		return best;
	}
	
	/**
	 * returns the distance to the nearest past terminal
	 * @return
	 */
	public int getOldTerminalDistance(){
		this.setUpFields();
		this.setUpBMBoards();
		int minDistance = Math.max(this.maxCol, this.maxRow) * 4;
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				if(!this.terminals[r][c] && this.pastTerminals[r][c]){
					int d = this.calculateDistance(this.currRow, this.currColumn, r, c);
					if(d < minDistance)
						minDistance = d;
				}
			}
		}
		return minDistance;
	}
	
	public double estimatePastTerminal(int numMovesLeft, double estDistance, int commitTime){
		this.setUpFields();
		this.setUpBMBoards();
		nextDirObject best = new nextDirObject();
		best.expectedUtility = this.penaltyDeath;
		nextDirObject temp = null;
		
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				if(!this.terminals[r][c] && this.pastTerminals[r][c]){
					int d = this.calculateDistance(this.currRow, this.currColumn, r, c);
					if(d < commitTime+this.manhattan){
						temp =this.getNextDirection(this.theBoard, this.terminals, walls, null, 
								explored, r, c,(int) (numMovesLeft-estDistance),
								this.penaltyDeath, this.maxRow, this.maxCol, (int) (numMovesLeft-estDistance), 0);
						if(temp.expectedUtility > best.expectedUtility)
							best = temp;
					}
				}
			}
		}
				
		return best.expectedUtility;
	}
	
	
	/* This is a modified version that says just give weight to those squares from
	 * which I can view one of the previous terminals that are no longer terminals
	 */
	public nextDirObject nextMoveA(double commitTime, double softCommitTime, double probCutOff){
		this.setUpFields();
		this.setUpBMBoards();
		/* we need to give values to squares where we can view if there exists a terminal
		 * and there is not a terminal within reach of us
		 * */
		boolean excludeSquare[][] = new boolean[this.maxRow][this.maxCol];
		boolean modTerminals[][] = new boolean[this.maxRow][this.maxCol];	// where we can explore from
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				for(int x=0; x< this.maxRow; x++){
					for(int y=0; y<this.maxCol; y++){
						if(terminals[x][y]){
							double prob = this.probReachSquareFromTo(r, c, x, y, (int)Math.max(commitTime, softCommitTime)).probSuccess;
							if(prob > probCutOff)	// we can reach this terminal with high probabilty
								excludeSquare[r][c] = true;
						}
						if(this.isReachableWithin(r, c, x, y, (int) commitTime) && this.pastTerminals[r][c] && !terminals[r][c])
							modTerminals[r][c] = true;
					}
				}
			}
		}
		/* now we know what squares have a terminal reachable, so weight all the
		 * squares with values that can possibly reach them*/
		double values[][] = new double[this.maxRow][this.maxCol];
		for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				if(excludeSquare[r][c])
					values[r][c] = 0;
				for(int x=0; x< this.maxRow; x++){
					for(int y=0; y<this.maxCol; y++){
						if(pastTerminals[x][y] && this.isReachable(r, c, x, y) && !terminals[x][y] && !terminals[r][c]){
							values[r][c] = 1;	// remember cant explore if we reach a terminal state
						}
					}
				}
			}
		}
		
		// modTerminals are true from where we will expose a past terminal, 
		// and we dont count other terminals as good places to go
	/*	for(int r=0; r<this.maxRow; r++){
			for(int c=0; c<this.maxCol; c++){
				modTerminals[r][c]= !excludeSquare[r][c] && modTerminals[r][c];
			}
		}*/
		
		/* now we maximize our utility */ // first arg might be values
		nextDirObject myMoves = this.getNextDirection(new double[maxRow][maxCol], modTerminals, walls, null, explored, this.currRow,
				this.currColumn, this.commitTime, 0, this.maxRow, this.maxCol, this.commitTime, 0);
		myMoves.probSuccess = myMoves.expectedUtility;
		myMoves.adviseMove = true;
		return myMoves;
	}
}
